﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;

namespace SharpBroadcast
{
    public class MpegHandler : MediaHandler
    {
        public const string HEADER_TAG = "jsmp";
        public const Int16 VIDEO_DEFAULT_WIDTH = 320;
        public const Int16 VIDEO_DEFAULT_HEIGHT = 240;

        protected Dictionary<string, byte[]> m_Headers = new Dictionary<string, byte[]>();

        public MpegHandler()
        {
            // ...
        }

        private static byte[] GenHeader(Int16 videoWidth, Int16 videoHeight)
        {
            byte[] header = null;
            using (MemoryStream stream = new MemoryStream())
            {
                byte[] tag = ASCIIEncoding.Default.GetBytes(HEADER_TAG);
                byte[] width = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(videoWidth));
                byte[] height = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(videoHeight));

                stream.Write(tag, 0, tag.Length);
                stream.Write(width, 0, width.Length);
                stream.Write(height, 0, height.Length);

                byte[] buffer = stream.ToArray();

                header = new byte[buffer.Length];
                Buffer.BlockCopy(buffer, 0, header, 0, header.Length);
            }
            return header;
        }

        public override byte[] GetWelcomeData(string param)
        {
            byte[] data = null;

            lock (m_Headers)
            {
                if (m_Headers.ContainsKey(param)) data = m_Headers[param];
            }

            return data;
        }

        public override void HandleInput(HttpListenerContext ctx)
        {
            try
            {
                if (ctx == null || MediaServer == null) return;
                if (ctx.Request.Url.Segments.Length <= 1) return;

                List<object> clients = null;
                string sourceName = ctx.Request.Url.Segments[1].Replace("/", "");

                if (sourceName.Length > 0)
                {
                    Int16 width = VIDEO_DEFAULT_WIDTH;
                    Int16 height = VIDEO_DEFAULT_HEIGHT;

                    string address = ctx.Request.RemoteEndPoint.ToString();

                    // update header
                    lock (m_Headers)
                    {
                        if (m_Headers.ContainsKey(sourceName)) m_Headers.Remove(sourceName); // refresh it

                        if (ctx.Request.Url.Segments.Length > 3)
                        {
                            try
                            {
                                width = Convert.ToInt16(ctx.Request.Url.Segments[2].Replace("/", ""));
                                height = Convert.ToInt16(ctx.Request.Url.Segments[3].Replace("/", ""));
                            }
                            catch
                            {
                                width = VIDEO_DEFAULT_WIDTH;
                                height = VIDEO_DEFAULT_HEIGHT;
                            }
                        }

                        byte[] header = GenHeader(width, height);
                        m_Headers.Add(sourceName, header);
                    }

                    // update state

                    MediaChannelState state = new MediaChannelState();
                    state.SourceName = sourceName;
                    state.SourceAddress = address;
                    state.MediaSize = width + "x" + height;
                    state.ClientCount = 0;

                    MediaServer.UpdateState(sourceName, state);

                    // get clients
                    clients = MediaServer.GetClients(sourceName);
                }

                if (clients == null) return;

                Queue<BufferData> cache = new Queue<BufferData>();

                MediaDispatcher dispatcher = new MediaDispatcher(MediaServer, clients);

                while (MediaServer.IsWorking())
                {
                    int realSize = 0;
                    byte[] data = new byte[MediaServer.OutputBufferSize];
                    try
                    {
                        realSize = ctx.Request.InputStream.Read(data, 0, data.Length);
                    }
                    catch (Exception ex)
                    {
                        realSize = 0;
                        MediaServer.Logger.Error("HTTP input error: " + ex.Message);
                    }

                    if (realSize <= 0) break;

                    BufferData buffer = new BufferData(data, realSize);

                    if (MediaServer.CacheQueueSize > 0)
                    {
                        cache.Enqueue(buffer);
                        if (cache.Count > MediaServer.CacheQueueSize)
                        {
                            dispatcher.Buffers.Enqueue(cache.Dequeue());
                            dispatcher.Process();
                        }
                    }
                    else
                    {
                        dispatcher.Buffers.Enqueue(buffer);
                        dispatcher.Process();
                    }

                }

                if (dispatcher != null) dispatcher.Stop();

                try
                {
                    ctx.Response.Close();
                }
                catch (Exception ex)
                {
                    MediaServer.Logger.Error("HTTP context error: " + ex.Message);
                }

                if (sourceName.Length > 0)
                {
                    // update header
                    lock (m_Headers)
                    {
                        if (m_Headers.ContainsKey(sourceName)) m_Headers.Remove(sourceName);
                    }

                    MediaServer.RemoveState(sourceName);
                }
            }
            catch (Exception ex)
            {
                MediaServer.Logger.Error("HTTP process error in thread: " + ex.Message);
            }
        }
    }
}
